package com.itclass.auth.authentication.provider.support;

import com.itclass.auth.authentication.provider.AbstractUserDetailsAuthenticationProvider;
import com.itclass.auth.authentication.token.supprt.UsernamePasswordLoginAuthenticationToken;
import com.itclass.auth.pojo.dto.AuthUserDetails;
import com.itclass.auth.pojo.dto.UserDetailsDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class UsernamePasswordAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider<UsernamePasswordLoginAuthenticationToken> {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    public UsernamePasswordAuthenticationProvider(UserDetailsService userDetailsService,PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordLoginAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();
        // 密码匹配
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }

    @Override
    protected void check(Authentication authentication) {
        Assert.isInstanceOf(UsernamePasswordLoginAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordLoginAuthenticationToken is supported");
        });
    }

    @Override
    protected UserDetails retrieveUser(String username, Authentication authentication) throws AuthenticationException {

        try {
            UserDetails loadedUser = userDetailsService.loadUserByUsername(username);
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            /**
             * 防止恶意用户 利用时间差来攻击
             *
             * 解释：恶意用户会根据随便输入一个账号来，他们会根据响应的时间来知道到底是账号错误，还是密码错误！这步骤就是解决这个问题的
             */
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    private void mitigateAgainstTimingAttack(Authentication authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            passwordEncoder.matches(presentedPassword,"10086");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordLoginAuthenticationToken.class.isAssignableFrom(authentication));
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        UsernamePasswordLoginAuthenticationToken result = new UsernamePasswordLoginAuthenticationToken();
        result.setPrincipal(user.getUsername());
        UserDetailsDTO userDetailsDTO = UserDetailsDTO.buildUserDetail((AuthUserDetails) user);
        result.setDetails(userDetailsDTO);
        result.setAuthorities(userDetailsDTO.getRoles());

        return result;
    }
}
